package com.atguigu.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.SecKillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.LoginRespEntity;
import com.atguigu.gulimall.seckill.feign.SeckillCouponFeignService;
import com.atguigu.gulimall.seckill.feign.SeckillProductFeignService;
import com.atguigu.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.seckill.service.SeckillService;
import com.atguigu.gulimall.seckill.to.SeckillSkuRedisTo;
import com.atguigu.gulimall.seckill.vo.SeckillSessionsWithSkus;
import com.atguigu.gulimall.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Classname SeckillServiceImpl
 * @Description 上架
 * @Date 2021/10/24 20:52
 * @Author Anonymous
 */
@SuppressWarnings("all")
@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private SeckillCouponFeignService seckillCouponFeignService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private SeckillProductFeignService seckillProductFeignService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //存储的秒杀场次对应数据
    //K: SESSION_CACHE_PREFIX + startTime + "_" + endTime
    //V: sessionId+"-"+skuId的List
    private final String SESSION_CACHE_PREFIX = "seckill:sessions:";

    //存储的秒杀商品数据
    //K: 固定值SECKILL_CHARE_PREFIX
    //V: hash，k为sessionId+"-"+skuId，v为对应的商品信息SeckillSkuRedisTo
    private final String SECKILL_CHARE_PREFIX = "seckill:skus";

    //K: SKU_STOCK_SEMAPHORE+商品随机码
    //V: 秒杀的库存件数
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";    //+商品随机码

    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1、去数据库扫描最近三天需要上架的活动
        R lates3DaySession = seckillCouponFeignService.getLates3DaySession();
        if (lates3DaySession.getCode() == 0) {
            //远程调用成功,,上架的商品
            List<SeckillSessionsWithSkus> sessionData = lates3DaySession.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {
            });
            //缓存到redis
            //1、缓存活动信息
            saveSessionInfo(sessionData);
            //2、缓存活动的关联商品信息
            saveSessionSkuInfo(sessionData);
        }
    }


    private void saveSessionInfo(List<SeckillSessionsWithSkus> sessions) {
        if (sessions != null) {
            sessions.stream().forEach(session -> {
                long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                //key
                String key = SESSION_CACHE_PREFIX + startTime + "-" + endTime;
                Boolean hasKey = stringRedisTemplate.hasKey(key);
                //避免重复上架
                if (!hasKey) {
                    //收集每个场次对应的ids作为value
                    List<String> skuIds = session.getRelationSkus().stream().map(item -> {
                        return item.getPromotionSessionId() + "-" + item.getSkuId();
                    }).collect(Collectors.toList());
                    // 缓存活动信息
                    stringRedisTemplate.opsForList().leftPushAll(key, skuIds);
                }
            });
        }
    }

    private void saveSessionSkuInfo(List<SeckillSessionsWithSkus> sessions) {
        if (sessions != null) {
            // 遍历session
            sessions.stream().forEach(session -> {
                        //获取hash操作
                        BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
                        // 遍历sku
                        session.getRelationSkus().stream().forEach(skus -> {
                            // 缓存中没有再添加
                            if (!hashOps.hasKey(skus.getPromotionSessionId() + "-" + skus.getSkuId())) {
                                SeckillSkuRedisTo seckillSkuRedisTo = new SeckillSkuRedisTo();
                                //设置商品的详细属性信息
                                R skuInfo = seckillProductFeignService.info(skus.getSkuId());
                                if (skuInfo.getCode() == 0) {
                                    SkuInfoVo skuInfoVo = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                                    });
                                    seckillSkuRedisTo.setSkuInfoVo(skuInfoVo);
                                }
                                //设置开始和结束时间
                                seckillSkuRedisTo.setStartTime(session.getStartTime().getTime());
                                seckillSkuRedisTo.setEndTime(session.getEndTime().getTime());
                                //将属性相同的属性拷贝
                                BeanUtils.copyProperties(skus, seckillSkuRedisTo);
                                //设置随机码
                                String randomCode = UUID.randomUUID().toString().replace("-", "");
                                seckillSkuRedisTo.setRandomCode(randomCode);
                                //每个商品都有自己的信号量，随机码
                                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                                // 在信号量中设置秒杀数量  商品可以秒杀的数量作为信号量
                                semaphore.trySetPermits(skus.getSeckillCount().intValue());
                                //存储到redis
                                hashOps.put(skus.getPromotionSessionId() + "-" + skus.getSkuId(), JSON.toJSONString(seckillSkuRedisTo));
                            }
                        });
                    }
            );
        }
    }


    /**
     * 返回当前时间可以参与的秒杀商品信息
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSecKillSkus() {
        //获得当前时间
        long now = new Date().getTime();
        //拿到所有SESSION_CACHE_PREFIX为前缀的key  [seckill:sessions:1635213600000-1635217200000, seckill:sessions:1635292800000-1635300000000]
        Set<String> keys = stringRedisTemplate.keys(SESSION_CACHE_PREFIX + "*");
//        System.out.println(keys);
        //遍历这些key
        for (String key : keys) {
            String replace = key.replace("seckill:sessions:", "");//1635213600000-1635217200000
            String[] split = replace.split("-");
            //拿到开始和结束时间
            Long startTime = Long.parseLong(split[0]);
            Long endTime = Long.parseLong(split[1]);
            if (now >= startTime && now <= endTime) {
                //拿到所有SESSION_CACHE_PREFIX为前缀的key的value  ["3-19","4-20"]
                List<String> range = stringRedisTemplate.opsForList().range(key, 0, 100);
                //为SECKILL_CHARE_PREFIX绑定一个hash操作
                BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
                //得到所有的商品信息
                List<String> string = hashOps.multiGet(range);
                if (string != null) {
                    return string.stream().map(item -> {
                        SeckillSkuRedisTo redisTo = JSON.parseObject(item, SeckillSkuRedisTo.class);
                        return redisTo;
                    }).collect(Collectors.toList());
                }
                //如果没有找到当前场次，直接break
                break;
            }
        }
        return null;
    }

    /**
     * 获取当前sku的秒杀信息
     */
    @Override
    public SeckillSkuRedisTo getSeckillSkuInfo(Long skuId) {
        //绑定hash操作
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
        //拿到所有的key
        Set<String> keys = hashOps.keys();
        if (keys != null && keys.size() > 0) {
            //对key进行遍历
            for (String key : keys) {
                if (Pattern.matches("\\d-" + skuId, key)) {
                    //如果匹配到了
                    String v = hashOps.get(key);//得到商品的具体信息
                    SeckillSkuRedisTo redisTo = JSON.parseObject(v, SeckillSkuRedisTo.class);

//                    Long startTime = redisTo.getStartTime();
//                    Long endTime = redisTo.getEndTime();
//                    Date dateStart = new Date(startTime);
//                    Date dateEnd = new Date(startTime);
//                    String pattern = "yyyy-MM-dd HH:mm:ss";
//                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
//                    String format0 = simpleDateFormat.format(dateStart);
//                    String format1 = simpleDateFormat.format(dateEnd);
//                    System.out.println("================" + format0 + "================" + format1);//================21-10-27 上午10:00

                    //查看是否在秒杀时间范围
//                    Long now = new Date().getTime();
                    long now = System.currentTimeMillis();
                    if (redisTo.getStartTime() < now && redisTo.getEndTime() > now) {
                        //如果在秒杀范围内，可以直接返回
                        return redisTo;
                    }
                    //如果不在秒杀范围内，要隐藏随机码返回
                    redisTo.setRandomCode(null);
                    return redisTo;
                }
            }
        }
        return null;
    }

    /**
     * 秒杀  成功之后返回一个订单号即可
     * http://seckill.gulimall.com/kill?killId=4-19&key=340318b0c7c84cb4a590a13d1f4e5bf2&num=1
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        long s1 = System.currentTimeMillis();
        LoginRespEntity user = LoginUserInterceptor.threadLocal.get();
        //1、登录判断，已经经过拦截器实现
        //2、合法性校验
        //2.1、秒杀时间
        //绑定hash操作
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
        String productInfo = hashOps.get(killId);//拿到商品信息
        SeckillSkuRedisTo redisTo = JSON.parseObject(productInfo, SeckillSkuRedisTo.class);
        Long startTime = redisTo.getStartTime();
        Long endTime = redisTo.getEndTime();
        Long currentTime = new Date().getTime();
        if (currentTime >= startTime && currentTime <= endTime) {
            //秒杀时间验证通过
            //2.2、随机码
            String randomCode = redisTo.getRandomCode();
            String skuId = redisTo.getPromotionSessionId() + "-" + redisTo.getSkuId();
            if (randomCode.equals(key) && killId.equals(skuId)) {
                //随机码验证通过
                BigDecimal seckillLimit = redisTo.getSeckillLimit();//每个用户最多买的数量
                if (num <= seckillLimit.intValue()) {
                    // 3.验证这个人是否已经购买过了，幂等性如果只要秒杀成功，就去占位。
                    String redisKey = user.getId() + "-" + skuId;
                    // 让数据自动过期，占位的超时时间
                    long ttl = redisTo.getEndTime() - redisTo.getStartTime();
                    Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl < 0 ? 0 : ttl, TimeUnit.MILLISECONDS);
                    if (aBoolean) {
                        //说明用户没有购买过
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                        boolean tryAcquire = semaphore.tryAcquire(num);//试着从redis中扣减预热的库存
                        if (tryAcquire) {
                            //如果成功  保存订单，发送MQ消息，返回订单号
                            // 秒杀成功
                            // 快速下单 发送MQ
                            String orderSn = IdWorker.getTimeId() + UUID.randomUUID().toString().replace("-", "").substring(7, 8);
                            SecKillOrderTo orderTo = new SecKillOrderTo();
                            orderTo.setOrderSn(orderSn);
                            orderTo.setMemberId(user.getId());
                            orderTo.setNum(num);
                            orderTo.setSkuId(redisTo.getSkuId());
                            orderTo.setSeckillPrice(redisTo.getSeckillPrice());
                            orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                            rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);
                            long s2 = System.currentTimeMillis();
                            log.info("耗时..." + (s2 - s1));
                            return orderSn;
                        }
                    }
                }
            }
        }
        long s3 = System.currentTimeMillis();
        log.info("耗时..." + (s3 - s1));
        return null;
    }
}
